// Copyright (C) 2008 Taylor L. Riche <riche@cs.utexas.edu>
//  
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
// 
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
// 
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software 
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
//

/**
 * 
 */
package appcomp.entities;

import java.net.URI;
import java.util.ArrayList;
import java.util.List;
import java.util.HashMap;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.PrintStream;
import java.lang.Exception;

import core.CodeGenerator;
import core.Entity;

/**
 * @author riche
 * 
 */
public class Operator extends Entity {

	private boolean isStateful;
	
	private boolean inComposite;

	private State state;

	protected ArrayList<Port> portList;

	protected HashMap<String, Boolean> includeMap;
	
	private HashMap<String, Double> delayMap;
	
	private double slack;
	
	private int maxQueueLength;
	
	private String slackPortName;
	
	private URI includeFile;
	
	private final ArrayList<String> methodList;
	
	protected int index;
	
	public void setIndex(int i) {this.index = i;}
	public int getIndex() {return this.index;}

	public Operator() {
		isStateful = false;
		state = null;
		portList = new ArrayList<Port>();
		methodList = new ArrayList<String>();
	}

	/**
	 * @param name
	 * @param isStateful
	 * @param state
	 * @param portList
	 */
	protected Operator(final String name, final Boolean isStateful,
			final State state, final ArrayList<Port> portList, final ArrayList<String> methodList) {
		this.setName(name);
		this.isStateful = isStateful;
		this.state = state;
		this.portList = portList;
		this.includeMap = new HashMap<String, Boolean>();
		this.delayMap = new HashMap<String, Double>();
		this.slack = Double.MAX_VALUE;
		this.maxQueueLength = Integer.MIN_VALUE;
		this.inComposite = false;
		this.methodList = methodList;
	}

	public static Operator create(String name, State state,
			ArrayList<Port> portList, ArrayList<String> methodList) {
		Boolean isStateful = false;
		if (state != null) {
			isStateful = true;
		}
		return new Operator(name, isStateful, state, portList, methodList);
	}

	public Type getMasterRequestType() {
		return null;
	}
	
	public void setMasterRequestType(Type t) {
		
	}
	
	/**
	 * @return the isStateful
	 */
	public Boolean getIsStateful() {
		return isStateful;
	}

	/**
	 * @return the state
	 */
	public State getState() throws Exception {
		if (!isStateful) {
			throw new Exception();
		}
		return state;
	}

	/**
	 * @return the portList
	 */
	public ArrayList<Port> getPortList() {
		return portList;
	}
	
	/**
	 * 
	 * @param portName
	 * @param conn
	 */
	public void registerConnection(String portName, Connector conn) {
		// TLR(2008/03/11): yes, it's a hack... didn't feel like add a name->Port map 
		// for a set that is usually quite small (< 10)
		for(Port p : this.portList) {
			if (p.getName().equals(portName)) {
				p.setConn(conn);
				break;
			}
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see appcomp.entities.Entity#generateCode()
	 */
	@Override
	public void generateCode(CodeGenerator cg) {
		String filename = "lc_" + this.getName() + "_Operator.h";
		FileOutputStream out = null;
		PrintStream file = null;
		try {
		    out = new FileOutputStream(filename);
		    file = new PrintStream(out);
		}
		catch (FileNotFoundException e) {
		    System.err.println("Specified file " + filename + " does not exist.");
		    System.exit(1);
		}

		// Print guards
		file.println("#ifndef LC_" + this.getName().toUpperCase() + "_H_");
		file.println("#define LC_" + this.getName().toUpperCase() + "_H_");
		file.println();

		// Print standard header info
		file.println("#include <assert.h>");
		file.println("#include <lagniappe/Operator.h>");
		file.println("#include <lagniappe/RCon.h>");
		file.println("#include <lagniappe/RData.h>");
		file.println("#include \"cmdline.h\"");

		// Print includes for incoming and outgoing data types
		for(Port p : this.portList) {
			p.setFile(file);
			p.generateInclude(this.includeMap);
		}
		try {
		    if(this.getState().getFileURI() != null) {
		        file.println("#include \"" + this.getState().getFileURI().toString() + "\"");
		    }
		}
		catch (Exception e)
		{
			
		}
		if(includeFile != null) {
			file.println("#include \"" + includeFile.toString() + "\"");
		}

		// Start class definition
		file.println();
		file.println("class " + this.getName() + " : public lagniappe::Operator {");
		
		// private data declaration
		file.println("private:");
		if(this.isStateful) {
			// yeah, I don't like this either
			// passing the file output stream is a little clunky
			state.setFile(file);
			state.generateCode(cg);
			if(this.state.getFlowSig() != null) {
			    file.println("\tvirtual void reassignFlow(lagniappe::ProcElement * proc) {");
			    file.println("\t\tfor(std::map<lagniappe::FlowID, std::pair<lagniappe::ProcElement *, unsigned int> *>::iterator i = flowMap.begin(); i != flowMap.end(); ++i) {");
			    file.println("\t\t\tif(i->second->first == proc) {i->second->first = procList[static_cast<unsigned long>(i->first % procList.size())]; i->second->second = procList.size(); break;}");
			    file.println("\t\t}");
			    file.println("\t}");
			}
		}

		// define handlers as private methods
		file.println();
		if(this.getClass() != RequestGenerator.class) {
			for(Port p : this.portList) {
				p.generateCode(cg);
			}
		}
		
		if(!methodList.isEmpty()) {
			for(String method : methodList) {
				file.println("\t" + method + ";");
			}
		}
		file.println("gengetopt_args_info * args;");
		// start public methods
		file.println();
		file.println("public:");
		
		file.println("\t" + this.getName() + "(std::string s, lagniappe::Mutex * m, lagniappe::System * sys, void * arg_struct, bool prof = false) : lagniappe::Operator(s, m, sys, prof) {");
		
		file.println("\t\tif(arg_struct != NULL) args = (gengetopt_args_info *)arg_struct;");
		if(this.isStateful) {
			file.println("\t\tthis->init();");
		}
		file.println("\t\tdestVec.resize(" + (this.portList.size()+2) + ");");
		file.println("\t}");
		
		if(this.isStateful) {
			// override data maintenance methods
			try {
				// TODO add flow signature information in these method arguments
				file.println("\tvoid install(void * data) {" + this.getState().getInstall() + "(data);}");
				file.println("\tvoid * get() {return " + this.getState().getGet() + "();}");
				file.println("\tvoid purge(void * data) {" + this.getState().getPurge() + "(data);}");
				file.println("\tvoid init() {" + this.getState().getInit() + "();}");
			}
			catch (Exception e) {}
			file.println("\tvirtual lagniappe::ProcElement * getProc(lagniappe::RCon * rcon) {");
			file.println("\t\tassert(!procList.empty());");
			file.println("\t\tassert(rcon->getRData() != NULL);");
			file.println("\t\tbool roundRobin = true;");
			file.println("\t\tlagniappe::ProcElement * retVal = NULL;");
			file.println("\t\tstd::pair<lagniappe::ProcElement *, unsigned int> * retPair = NULL;");
			if(this.state.getFlowSig() != null) {
				for(Port p : this.portList) {
					if(p.getDirection() == Port.Direction.IN) {
						if(p.getType().getCoreType().equals(this.state.getFlowSig().getRefType())) {
							// if Rcon->port = p
							file.println("\t\tif(rcon->getPort() == \"" + p.getName() + "\") {");
							// call getFlowID(RCon->data)
							// lookup flow id in table, if not there, make new entry, if is there, send request to processor in table
							// we now have a flow map that keeps track of flow pinnings
							file.println("\t\t\tlagniappe::FlowID fid = getFlowID(static_cast<" + p.getType().getCoreType() + " *>(rcon->getRData()));");
							file.println("\t\t\tif((retPair = flowMap[fid]) == NULL) {");
							file.println("\t\t\t\tretPair = flowMap[fid] = new std::pair<lagniappe::ProcElement *, unsigned int>(procList[static_cast<unsigned long>(fid % procList.size())], procList.size());");
							file.println("\t\t\t}");
							file.println("\t\t\telse if(retPair->second < procList.size()) {");
							file.println("\t\t\t\tretPair->first = procList[static_cast<unsigned long>(fid % procList.size())];");
							file.println("\t\t\t\tretPair->second = procList.size();");
							file.println("\t\t\t}");
							// following line redoes pinning every time a resource is added/removed, but has no state
							//file.println("\t\t\tretVal = procList[static_cast<unsigned long>(fid % procList.size())];");
							file.println("\t\t\tretVal = retPair->first;");
							// set key to false
							file.println("\t\t\troundRobin = false;");
							file.println("\t\t}");
						}
					}
				}
			}
			file.println("\t\tif (roundRobin) {");
			file.println("\t\t\tint size = procList.size();");
			file.println("\t\t\tint rnum = (int)(size * (rand() / (RAND_MAX + 1.0)));");
			file.println("\t\t\tassert(rnum < size);");
			file.println("\t\t\tretVal = procList[rnum];");
			file.println("\t\t}");
			file.println("\t\treturn retVal;");
			file.println("\t}");
			file.println();
			if(this.state.getFlowSig() != null) {
				file.println("\tlagniappe::FlowID getFlowID(" + this.state.getFlowSig().getRefType() + "* data) {");
				// use the getFlowID method provided by the user
				file.println("\t\tlagniappe::FlowID retval = data->" + this.state.getFlowSig().getGetFlowID() + "();");
				//file.println("\t\tdelete data;");
				file.println("\t\treturn retval;");
				file.println("\t}");
			}
		}
		
		file.println();
		file.println("\tvirtual void processRequest(lagniappe::RCon * rcon) {");
		file.println();
		if(!this.doesGenerate()) {
			for(Port p : this.portList) {
				if(p.getDirection() == Port.Direction.IN) {
					file.print("\t\tif(rcon->getPort() == ");
					p.generateCodeCases();
				}
			}
		}
		file.println("\t\tif(rcon->getPort() == 0) {");
		file.println("\t\t\tLC_handleMonitor(rcon->getRData());");
		file.println("\t\t}");
		file.println("\t\tdelete rcon;");
		file.println("\t}");
		
		// register queues
		file.println("\tvirtual void registerPorts(lagniappe::CommChannel * chan) {");
		for(Port p : this.portList) {
			if(p.getDirection() == Port.Direction.IN) {
				file.println("\t\tchan->registerQueue(" + p.getIndex() + ");");
			}
		}
		file.println("\t\tchan->registerQueue(0);");
		file.println("\t\t");
		file.println("\t}");
		
		// Generate list of RCons
		file.println();
//		if(!this.doesGenerate()) {
//			file.println("\tvirtual std::list<lagniappe::RCon *> * generateRequestList() {");
//			file.println("\t\tstd::list<lagniappe::RCon *> * retList = new std::list<lagniappe::RCon *>;");
//			for(Port p : this.portList) {
//				if(p.getDirection() == Port.Direction.IN) {
//					file.println("\t\tretList->push_back(new lagniappe::RCon(this, static_cast<lagniappe::RData *>(gen" + p.getName() + "Request()), \"" + p.getName() + "\"));");
//				}
//			}
//			file.println("\t\treturn retList;");
//			file.println("\t}");
//		}
		
		if(this.maxQueueLength > Integer.MIN_VALUE) {
			file.println("\tvirtual unsigned int getQueueThreshold() {return " + this.maxQueueLength + ";}");
		}
		
		Boolean hasPred = false;
		for(Port p : this.portList) {
		    if(p.getPredicate() != null) {
		        hasPred = true;
		        file.println("\tbool " + p.getPredicate() + "();");
		    }
		}
		if(hasPred) {
		    file.println("\tvirtual bool pred(std::string port) {");
		    for(Port p : this.portList) {
		        if(p.getPredicate() != null) {
		            file.println("\t\tif(port == \"" + p.getName() + "\") {return " + p.getPredicate() + "();}");
		        }
		    }
            file.println("\t\treturn true;");
		    file.println("\t}");
		}
		
		// Close class definition
		file.println();
		file.println("};");
		
		// Close guards
		file.println("#endif");
		file.println();
		
		
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see core.Entity#generateFrameworkCode()
	 */
	@Override
	public void generateFrameworkCode(PrintStream file) {
		file.println("\t\t{lagniappe::Operator * tempOp = new " + this.getName() + "(\"" + this.getName() + "\", sys->getNewMutex(), sys, arg_struct, prof);");
		file.println("\t\topVec.at(" + this.index + ") = tempOp;}");
	}

	public void generateIncludes(PrintStream file) {
		file.println("#include \"lc_" + this.getName() + "_Operator.h\"");
	}

	/**
	 * @return the delay
	 * @param port The name of the port attached to the handler in question
	 */
	public double getDelay(String port) {
		return delayMap.get(port);
	}

	/**
	 * @param delay the delay to set
	 * @param port the port associated with the handler
	 */
	public void setDelay(String port, double delay) {
		delayMap.put(port, delay);
	}
	
	// Don't use this method for anything
//	private void populateDelay() {
//		for(Port p : this.portList) {
//			if(p.getDirection() == Port.Direction.IN) {
//				delayMap.put(p.getName(), new Double(10));
//			}
//		}
//	}
	
	public boolean doesGenerate() {
		return false;
	}
	
	/**
	 * @return the slack
	 */
	public double getSlack() {
		return slack;
	}

	/**
	 * @param slack the slack to set
	 */
	public void setSlack(double slack, String portName) {
		if(slack < this.slack) {
			this.slack = slack;
			this.slackPortName = portName;
		}
	}

	public void translateSlack() {
		if (this.slack < Double.MAX_VALUE) {
			double delay = this.delayMap.get(this.slackPortName);
			if(delay == 0) {
				delay = 1;
			}
			this.maxQueueLength = new Double(Math.floor(this.slack / delay)).intValue();
			System.out.println(this.maxQueueLength);
		}
	}

	/**
	 * @return the maxQueueLength
	 */
	public int getMaxQueueLength() {
		return maxQueueLength;
	}

	/**
	 * @return the includeFile
	 */
	public URI getIncludeFile() {
		return includeFile;
	}

	/**
	 * @param includeFile the includeFile to set
	 */
	public void setIncludeFile(URI includeFile) {
		this.includeFile = includeFile;
	}
	
	public boolean isInComposite() {
		return inComposite;
	}

	public void joinedComposite() {
		inComposite = true;
	}
	
	public void consumeConnectors(List<Connector> oldList, List<Connector> newList) {
		// Don't do anything here
	}

	/**
	 * @param isStateful the isStateful to set
	 */
	public void setStateful(boolean isStateful) {
		this.isStateful = isStateful;
	}

	/**
	 * @param state the state to set
	 */
	public void setState(State state) {
		this.state = state;
	}

	/**
	 * @param portList the portList to set
	 */
	public void setPortList(ArrayList<Port> portList) {
		this.portList = portList;
	}
	
	public Port getPortByName(String name) {
		Port ret = null;
		for(Port p : this.portList) {
			if(p.getName().equals(name)) {
				ret = p;
			}
		}
		return ret;
	}
	
	public void printPorts() {
		System.out.println(this.getName());
		for(Port p : this.portList) {
			System.out.println("\t" + p.printFullName());
		}

	}
}
